import { Camera, Object3D, Quaternion, Raycaster, Vector2, Vector3 } from "../libs/three137/three.module.js";
import {JoyStick} from "../libs/JoyStick.js"
import { GUI } from "../libs/dat.gui.module.js";


let gui
//  gui = new GUI();



const { abs, max, min } = Math
const axeVal = 0.003921;
let preTime = 0 ;
class Controller {
    constructor(game) {
        this.camera = game.camera;
        this.clock = game.clock;
        this.user = game.user;
        this.target = game.user.root;
        this.navMesh = game.navMesh
        this.game = game;

        this.raycaster = new Raycaster();

        // 记录当前按下的键
        this.move = { up: 0, right: 0 };
        this.look = { up: 0, right: 0 };//  控制相机方向
        this.tempVec3 = new Vector3();
        this.temQuat = new Quaternion();
        // 相机的虚拟占位  后面会用这个物体的位置去更新相机的位置
        this.cameraBase = new Camera();
        this.cameraBase.position.set(.1,2,-6)
 if(gui){
        gui.add(this.cameraBase.position,'x' ,1,10).setValue(3)
        gui.add(this.cameraBase.position,'y' ,1,10).setValue(2)
        gui.add(this.cameraBase.position,'z' ,-5,5).setValue(-5)
    }
        
        
        this.highCamera = new Camera() ;
        this.highCamera.position.copy(this.game.camera.position) ;
        this.highCamera.position.y +=10 ;
        this.highCamera.lookAt(this.target.position);
        this.target.attach(this.highCamera)
        
        
        // attach 方法 和 add相比 ，就是 attach不会改变其原本的世界位， 也就是计算出object 相对于这个容器的矩阵 
        this.target.add(this.cameraBase) ;

        // 相机和光照的 lookat 和 其他 Object3d的视线刚好相反  相机是可以理解的     
        // lookAt 调用时会先求出调用者的 世界位  ，所以要保证传参是世界位 
        this.cameraBase.lookAt(this.target.position)
        
        this.yAxis = new Vector3(0, 1, 0);
        this.xAxis = new Vector3(1, 0, 0);
        this.forward = new Vector3(0, 0, 1);
        this.down = new Vector3(0, -1, 0);

        this.speed = 5;
        this.checkForGamepad();

        // if (document.documentElement.ontouchstart) {  虽然确实有这个属性，但是结果是null 
            if ('ontouchstart'  in document.documentElement ) {
            this.initOnscreenController()
        } else {
            this.initKeyboardController()
        }

    }
    // 触屏
    initOnscreenController() {
        const options1 = { 
            right:true ,
            app:this ,
            onMove:this.onMove
        };

        const joystick1 = new JoyStick(options1)
   
        const options2 = { 
            left:true ,
            app:this ,
            onMove:this.onLook
        };

        const joystick2 = new JoyStick(options2)

        const fireBtn  = document.createElement('button') ;
        fireBtn.style.cssText = "position:absolute; bottom:55px; width:40px; height:40px; background:#FFFFFF; border:#444 solid medium; border-radius:50%; left:50%; transform:translateX(-50%);";
        document.documentElement.append(fireBtn) ;
        
        fireBtn.addEventListener('pointerdown', this.fire.bind(this))

        this.touchController = { joystick1,joystick2, fireBtn}

    }

    initKeyboardController() {
        document.addEventListener('keydown', this.keyDown.bind(this))
        document.addEventListener('keyup', this.keyUp.bind(this))
        document.addEventListener('mousedown', this.mouseDown.bind(this))
        document.addEventListener('mouseup', this.mouseUp.bind(this))
        document.addEventListener('mousemove', this.mouseMove.bind(this))
        this.keys = {
            a: false, s: false, d: false, w: false,
            mousedown: false,
            mouseorigin: new Vector2(),
        }

    }
  //检查游戏手柄是否连接
    checkForGamepad() {
        const gamepads = {} // 单个
        const self  = this ;
        function gamepadConnect(event ,connecting) {
         const gamepad  =  event.gamepad ;
         if(connecting){ 
            gamepads[gamepad.index] = gamepad
            self.gamepad = gamepad 
            // 如果有手柄，触摸控件就可以隐藏了
            if(self.touchController) self.showTouchController(false)
         }else { 
            self.gamepad = undefined ;
            gamepads[gamepad.index] =undefined
            if(self.touchController) self.showTouchController(true)
         }   
        }
        // https://developer.mozilla.org/zh-CN/docs/Web/API/Gamepad
        // 第三个参数 option  usecapture  作为逻辑值时
      window.addEventListener('gamepadconnected', function(e){
        gamepadConnect(e, true)
      }, false)  

      window.addEventListener('gamepaddisconnected', function(e){ 
        gamepadConnect(e, false)
      },
      false )
      
    }
//  偶尔 会出现 两个摇杆同时起作用 是在初次使用一个摇杆后  再使用另一个就会如此
    showTouchController(mode) {
        if (!this.touchController) { return }
        this.touchController.joystick1.visible = mode;
        this.touchController.joystick2.visible = mode;
        this.touchController.fireBtn.style.display = mode ? 'block' : 'none';
    }

    keyDown(e) { // 按下不放会持续触发
        if(this.user.dead) return 
        // console.log(e);
        switch (e.key) {
            case 'w':
                this.keys.w = true;

                break;
            case 'a':
                this.keys.a = true;

                break;
            case 's':
                this.keys.s = true;

                break;
            case 'd':
                this.keys.d = true;

                break;
            case ' ':
                // repeat就代表是按住不放触发的事件，不是第一次
               if(!e.repeat) this.fire(true);
                break;

            default:
                break;
        }

    }

    keyUp(e) {
        switch (e.key) {
            case 'w':
                this.keys.w = false;
                if (!this.keys.s) { this.move.up = 0 }
                break;
            case 'a':
                // 在反方向控制键也没按下的情况下归零
                this.keys.a = false;
                if (!this.keys.d) { this.move.right = 0 }

                break;
            case 's':
                this.keys.s = false;
                if (!this.keys.w) { this.move.up = 0 }

                break;
            case 'd':
                this.keys.d = false;
                if (!this.keys.a) { this.move.right = 0 }

                break;
            case ' ':
                this.fire(false);
                break;

            default:
                break;
        }


    }

    mouseDown(e) {
        if(this.user.dead) return 
        // console.log(e);
        this.keys.mousedown = true;
        this.keys.mouseorigin.x = e.offsetX
        this.keys.mouseorigin.y = e.offsetY
    }

    mouseUp(e) {
        this.keys.mousedown = false;
        this.look.up = 0
        this.look.right = 0;

    }

    mouseMove(e) {
        if(this.user.dead) return 

        // 竟然有movementX Y 这样方便的值    offetX相对于容器内边界 应该是除掉了border 
        if (!this.keys.mousedown) return;
        let dx = e.offsetX - this.keys.mouseorigin.x;
        let dy = e.offsetY - this.keys.mouseorigin.y;
        // if(dx < -100 ) dx =-100 
        // if(dx > 100 ) dx = 100 
        console.log(e);
        dx = max(-100, min(100, dx));
        dy = max(-100, min(100, dy));
        dx /= 100; dy /= 100;
        this.onLook(-dy, dx) //  dx dy都是向量  这个是逆时针旋转90度啊 

    }

    fire(mode) {
        this.user.firing =mode;
    }

    onMove(up, right) {
        // console.log(up,right);
        this.move.up = up;
        this.move.right = -right

    }
    onLook(up, right) {
        console.log(up, right);
        this.look.up = up * .25;
        this.look.right = -right

    }

    gamepadHandler() {
        const gamepads = navigator.getGamepads() ;
        const gamepad = gamepads[this.gamepad.index] ;
        const leftStickX = gamepad.axes[0] +axeVal //  左摇杆 往左 最大-1   左负右正
        const leftStickY = gamepad.axes[1] + axeVal  // 左摇杆往前 最大-1     前负后正
        const rightStickX = gamepad.axes[2]-axeVal
        const rightStickY = gamepad.axes[3]
        const fire =  gamepad.buttons[7].pressed ;
        if(!preTime)preTime = performance.now()
        if(performance.now() - preTime > 2000){
            console.log(gamepad.axes);
            preTime = performance.now()
        }
        this.onMove(-leftStickY, leftStickX) ;
        this.onLook(-rightStickY, rightStickX)
        this.fire(fire)
    }
    /**
     * move是记录在地面这个二维平面内的移动速度  顺便可以合成方向
     * 
     */
    keyHandler() {
        if (this.keys.w) { this.move.up += .1 }
        if (this.keys.s) { this.move.up += -.1 }
        if (this.keys.a) { this.move.right += -.1 }
        if (this.keys.d) { this.move.right += .1 }
        // 限制速度最大值
        this.move.up = max(-1, min(1, this.move.up))
        this.move.right = max(-1, min(1, this.move.right))
    }
    update(dt = .0167) {
        if(this.user.dead) return ;
        let playMoved = false;// 玩家是否处于移动状态
        let speed;
        if (this.gamepad) {
            this.gamepadHandler();
        } else if (this.keys) {
            this.keyHandler()
        }

        if (this.move.up !== 0) {
            const forward = this.forward.clone().applyQuaternion(this.target.quaternion); // 本地 z轴的世界位
            speed = this.move.up > 0 ? this.speed * dt : this.speed * dt * .3; //  限制后退的速度只有前进的。3
            speed *= this.move.up; //  再乘上up方向的作甚 这才是速度 不考虑横向移动的速度
            const pos = this.target.position.clone().add(forward.multiplyScalar(speed));// z轴的长度这里就改变了啊
            pos.y += 2; // y+2作甚

            this.raycaster.set(pos, this.down);// 射线向下 是为了找人物在导航网格上的投影点，前面的y+2 也就可以理解了
            const intersects = this.raycaster.intersectObject(this.navMesh);

            if (intersects.length > 0) { // 如果在网络上
                this.target.position.copy(intersects[0].point);
                playMoved = true;
            }
        }

        if (abs(this.move.right) > .1) { //  现在的逻辑 按下至少就+。1 所以这个判断没毛病 
            const theta = dt * (this.move.right - .1) * 1; // 绝对值大于.1才行动， 但是这里是直接减 ,这样看来 等于是速度区间往左偏移了 。1
        
            this.target.rotateY(theta);
            playMoved = true;
        }


        if (playMoved) {
            this.cameraBase.getWorldPosition(this.tempVec3);
            this.camera.position.lerp(this.tempVec3, .7); //  第二个参数是类似斜率，需要注意的是， 这个参数类似一个指数的底数。

            let run = false;
            if (speed > .03) {
                if (this.overRunSpeedTime) { // 这个over就是某次初始化的时间
                    const elapsedTime = this.clock.elapsedTime - this.overRunSpeedTime;
                    run = elapsedTime > .1;// 起跑时间大于。1 ？
                } else {
                    this.overRunSpeedTime = this.clock.elapsedTime;
                }
            } else {
                this.overRunSpeedTime = 0; //  行动结束 重置这个时间
            }
            // 已经在移动了至少也是行走
            if (run) {
                this.user.action = 'run'
            } else {
                this.user.action = 'walk'
            }

        } else { // fix  开火的时候不要执行
            if (this.user  && !this.user.isFiring) { this.user.action = 'idle' }

        }


        // 镜头
        if (this.look.up === 0 && abs(this.look.right) < 0.00001) {

            let lerpSpeed = .7;
            this.cameraBase.getWorldPosition(this.tempVec3);

            let seeThrough  =true ;
            // 只有在不运镜的时候才需要判断角色的可见性
            if(!seeThrough && !this.game.seeUser(this.tempVec3)){ 
                this.highCamera.getWorldQuaternion(this.temQuat) ;
                this.highCamera.getWorldPosition(this.tempVec3)
            }else { 
                this.game.seeUser(this.tempVec3, seeThrough)
                this.cameraBase.getWorldQuaternion(this.temQuat);
            }

            this.camera.position.lerp(this.tempVec3, lerpSpeed);
            this.camera.quaternion.slerp(this.temQuat, lerpSpeed)
        } else { // 此时就是人物不动  旋转镜头
            // console.log(this.look.up);
            const delta = 1 * dt;
            // 不对  这里应该理解为，他想要先绕y轴 ，再绕本地的x轴旋转
            this.camera.rotateOnWorldAxis(this.yAxis, this.look.right * delta);// y  和 x的旋转分开了啊 
            const cameraXAxis = this.xAxis.clone().applyQuaternion(this.camera.quaternion); // y轴一直没变 所以上面直接，这里确要旋转
            this.camera.rotateOnWorldAxis(cameraXAxis, this.look.up * delta); // 左右和俯仰啊
        }
    }

    reset(){ 
        
    }

}

export { Controller }